Scripting Examples
In this section we will go through a number of practical examples of how scripts can be used with a Controller. These examples are all based on real projects that are installed and working. They do get progressively more involved, so do not worry if you don't follow the later ones - you will still be able to use script successfully to solve many problems.
Conditions
Running a Trigger 50% of the time
The script below can be used to only run the trigger 50% of the time randomly
-- returns true randomly, 50% of the time
return math.random(1,2) == 1
Actions
Cycling through different timelines
We are installing a wall of RGB LED fixtures in a children's play area. There is a single large button that the kids are supposed to press. Each time they press it they should get a different colour or effect on the wall.
Each colour or effect would be programmed as a different timeline in Designer. The button will connect to a contact closure and so we will have a single Digital Input trigger. Rather than starting a timeline directly we will instead run the following script:
-- which timelines should we cycle through? timeline = { 22, 14, 24, 16, 15, 17, 21 }-- on first time of running, initialise index if not index then index = 1 end-- start the timeline whose number is at entry 'index'get_timeline(timeline[index]):start()-- increment index index = index + 1-- should we go back to the beginning of the table? if index > #timeline then -- #timeline returns the number of values in the table index = 1 endCopy-- which timelines should we cycle through?
timeline = { 22, 14, 24, 16, 15, 17, 21 }
-- on first time of running, initialise index
if not index then
index = 1
end
-- start the timeline whose number is at entry 'index'
get_timeline(timeline[index]):start()
-- increment index
index = index + 1
-- should we go back to the beginning of the table?
if index > #timeline then -- #timeline returns the number of values in the table
index = 1
end
How would this change if we wanted each button press to choose a timeline at random rather than cycling through them in order?
-- which timelines should we cycle through? timeline = { 22, 14, 24, 16, 15, 17, 21 }-- use the random function to set index index = math.random(1,#timeline)-- start the timeline whose number is at entry 'index' get_timeline(timeline[index]):start()
Of course if the timeline selection is truly random then it will sometimes select the same timeline twice in a row. If we wanted to prevent this from happening how could we do it?
-- which timelines should we cycle through? timeline = { 22, 14, 24, 16, 15, 17, 21 }-- find an index different from the old one while index == oldIndex do -- use the random function to set index index = math.random(1,#timeline) end-- store the index for next time round oldIndex = index-- start the timeline whose number is at entry 'index' get_timeline(timeline[index]):start()
Stopping a Range of Timelines
We need to stop a large number of timelines in one go, but not all of them.
You can use up to 32 Actions on a Trigger, so if you need to stop more than 32 Timelines at once, you will need to use a script.
You can stop a single timeline from a script with the following:
get_timeline(1):stop()
This allows you to stop a single timeline from a script, but if you have a large number to stop, adding this for each timeline is a lot of work.
A FOR loop can be used to reduce the amount of scripting required.
for i=1,10 do -- run through the values 1-10 get_timeline(i):stop() -- Stop the timeline defined by i end
This script can be used to run through from 1 to 10 (or a different range by changing the values), and will stop the timeline with those numbers. To make this more useful, you can put it in a function which allows you to call it with any range of timeline numbers.
function stop_range(a, b) -- this defines the script as a function with two variables (a and b) for i=a,b do -- a FOR loop which runs through from a to b get_timeline(i):stop() -- stop the timeline defined by i end endstop_range(1,10) -- call the function with the variables 1 and 10
NOTE: It is generally best practice to define the function in a script that is run at startup, and then call the function when it is needed
Make a timeline loop N times
The designer has requested that a particular timeline runs once at sunset on a Monday, but twice at sunset on a Tuesday, three times at sunset on Wednesday, etc. He is planning to keep changing the timeline so does not want to have lots of copies.
There are actually lots of perfectly reasonable ways to solve this using script. Let's assume we have a single astronomical clock trigger that fires at sunset and runs the following script:
N = time.get_current_time().weekday -- 1 is Monday, 7 is Sundayget_timeline(1):start()
The timeline would be set to loop when it was programmed. We also put a flag on the timeline at the end and make a flag trigger that runs a second script:
-- decrement N N = N - 1if N == 0 then -- release timeline 1 in time 5s get_timeline(1):stop(5) end
Note how this works by setting the value of the variable N
in one script and then using that variable in another script, which is often a useful technique.
We have used two scripts here, but it is possible to do the same job using only one.
In this case you would have the sunset trigger start the timeline directly and use the following script on the flag trigger:
-- is this the first time round? if not N or N == 0 then N = time.get_current_time().weekday -- 1 is Monday, 7 is Sunday end-- decrement N N = N - 1if N == 0 then enqueue_trigger(2) -- runs action on trigger 2 end
The trick here is to detect whether it is the first time round the loop - if the Controller has started up today then N
will have no value and so not N
will be true
, otherwise N
will have been left with the value zero when the script ran yesterday. When we detect it is the first time then we set its initial value in the same way as before.
We have also used a different method to do the timeline release. Rather than calling get_timeline(num):stop()
directly from the script we are causing Trigger number 2 to fire. We can then configure Trigger number 2 to have an Action that releases the correct timeline. It is sometimes easier to write scripts like this, as it can be easier to see where to change properties. In this case all the you need to do is to modify the Start and Release Timeline Actions in the Trigger list if you want to change which timeline is run.
Storing Data to the Memory Card
In the event that a controller reboots, we want it to start running the timeline that was running prior to the reboot.
The Lua library contains functions that make it possible to read and write files to the device the Lua is running on. This includes reading and writing files on the Controller's Memory Card.
running = 1
This variable will be used to store the number of the timeline that was started most recently, using a Timeline Started Trigger (set to Any) with a Run Script action as below:
running = get_trigger_variable(1)
Storing data on the memory card involves two steps, writing to the card and reading back from the card.
function writeToCard() -- Write the running timelines table to the memory card file = io.open(get_resource_path("timeline.txt"), "w+") -- Open or create a file (in write mode) called timeline.txt if (file ~= nil) then -- Ensure the file has been opened io.output(file) -- Set the open file as the default output location for the io library io.write("running = " .. running) -- Write the line "running = [value]" to the file. The value will be the value of the variable io.flush() -- Clear the output buffer io.close() -- Close the file end end
Whenever the function writeToCard is called it will store the current value of the variable running to the memory card in a file called timeline.txt. The file will be in the format:
running = [value]
This is the syntax for defining a variable in Lua and means that if we run the file on startup, it will set the variable running to be the value stored in the file (with no parsing required)
function readFromCard() -- Read the stored running timelines table and start the timelines specified file = io.open(get_resource_path("timeline.txt"),"r") -- Open the file timeline.txt (in read mode) if it exists if file ~= nil then -- Ensure the file has been opened dofile(get_resource_path("timeline.txt")) -- Run the file to set the variable end get_timeline(running):start() -- Start the timeline stored in the running variable end
Whenever the function readFromCard is run, it will find the file called timeline.txt, run it so that the stored variable is set on the controller and then start the relevant timeline
NOTE: Functions should be placed in a Run Script action at Startup to ensure they are declared. They can then be used at any time within the show file.
Push data to the web interface
If you are using a Custom Web Interface, it is possible to push data to it from the project file e.g. when a TPC Slider is moved. You will then need to set up some JavaScript within your custom web interface to read the data in.
If we want to send the level of a MTPC slider to the web interface, we would use a Touch Slider Move Trigger set to match the Slider's Key. This would have a Run Script Action attached with the following Lua Script
level = get_trigger_variable(1) -- Capture the level set on the slider
push_to_web("slider_level",level) -- creates a JSON packet in the form {slider_level:level}, where level is the value stored previously
Then within the web interface, we need to use the subscribe_lua() function to process this data.
Implementing an interactive game for a Science Museum
In an exhibit children are posed questions and have to select answers from an array of numbered buttons. The buttons are large with RGB backlights that are controlled by a Controller to highlight choices and indicate right and wrong answers. Questions are displayed by a slide projector which is under RS232 control from the Controller. The buttons are wired to contact closures on the Controller and on RIOs, so that the Controller can check answers and determine the progress of the game accordingly. The lighting in the rest of the room is designed to mimic a popular TV quiz show to retain the children's interest, with different timelines for each stage of the game.
I am not going to work through this example - but the key point is that it should now be clear to you that a Controller could be used to implement this sort of advanced interactive exhibit with the use of script. Try breaking down the problem into discrete parts and you will find that no individual part of this is difficult - although getting it all to function together reliably would no doubt require a lot of work. The Controller is a viable alternative to custom software running on a PC and has clear advantages in terms of durability and cost.